/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *C
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Web;
using System.Threading;
using System.Collections.Generic;

using Gtk;
using Glade;
using Pango;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Client.GtkGui;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;

namespace Galaxium.Protocol.Msn.GtkGui
{
	public class MsnChatWidget : BasicChatWidget
	{
		protected P2PViewWebcam _webcam = null;
		protected ContactListView _contactList = null;
		
		DateTime _lastReceivedTime;
		uint _typingTimer = 0;
		
		internal MsnChatWidget (IContainerWindow<Widget> window, IConversation conversation) : base (window, conversation)
		{
			_conversation.Established += ConversationEstablished;
			_conversation.Closed += ConversationClosed;
			_conversation.ContactJoined += ConversationContactJoined;
			_conversation.ContactLeft += ConversationContactLeft;
			_conversation.AllContactsLeft += ConversationAllContactsLeft;
			_conversation.MessageReceived += ConversationMessageReceived;
			
			(_conversation as MsnConversation).InkReceived += ConversationInkReceived;
			(_conversation as MsnConversation).NudgeReceived += ConversationNudgeReceived;
			(_conversation as MsnConversation).TypingReceived += ConversationContactTyping;
			(_conversation as MsnConversation).WinkReceived += ConversationWinkReceived;
			(_conversation as MsnConversation).VoiceClipReceived += ConversationVoiceClipReceived;
			(_conversation as MsnConversation).MessageSendFailed += ConversationMessageSendFailed;
			(_conversation as MsnConversation).WebcamInviteReceived += ConversationWebcamInviteReceived;
			(_conversation as MsnConversation).CapabilitiesChanged += ConversationCapabilitiesChanged;
			
			conversation.EventReceived += HandleEventReceived;
			conversation.MessageReceived += HandleMessageReceived;
			
			_conversation.Session.Account.DisplayImageChange += AccountDisplayImageChanged;
		}
		
		public override void Initialize ()
		{ 
			base.Initialize ();
			
			ScrolledWindow treeWindow = new ScrolledWindow ();
			treeWindow.ShadowType = ShadowType.In;
			
			_contactList = new ContactListView (Conversation);
			_contactList.Manager = new ListContactTreeManager ();
			
			treeWindow.Add (_contactList);
			SetChatWidget (ChatWidgetPositions.RightDisplayPane, treeWindow);
			
			AdjustForContacts ();
			
			ProcessLogs ();
			
			Update();
			
			GtkActivityUtility.ClearTypeFromQueue (Conversation.PrimaryContact, ActivityTypes.Message);
			
			AddEventHandlers (Conversation.PrimaryContact as MsnContact);
			
			_nativeWidget.Show ();
		}
		
		public override void Destroy ()
		{
			base.Destroy ();
			
			RemoveEventHandlers (Conversation.PrimaryContact as MsnContact);
			
			_conversation.Established -= ConversationEstablished;
			_conversation.Closed -= ConversationClosed;
			_conversation.ContactJoined -= ConversationContactJoined;
			_conversation.ContactLeft -= ConversationContactLeft;
			_conversation.AllContactsLeft -= ConversationAllContactsLeft;
			
			_conversation.MessageReceived -= ConversationMessageReceived;
			
			(_conversation as MsnConversation).InkReceived -= ConversationInkReceived;
			(_conversation as MsnConversation).NudgeReceived -= ConversationNudgeReceived;
			(_conversation as MsnConversation).TypingReceived -= ConversationContactTyping;
			(_conversation as MsnConversation).WinkReceived -= ConversationWinkReceived;
			(_conversation as MsnConversation).VoiceClipReceived -= ConversationVoiceClipReceived;
			(_conversation as MsnConversation).MessageSendFailed -= ConversationMessageSendFailed;
			(_conversation as MsnConversation).WebcamInviteReceived -= ConversationWebcamInviteReceived;
			(_conversation as MsnConversation).CapabilitiesChanged -= ConversationCapabilitiesChanged;
			
			_conversation.Session.Account.DisplayImageChange -= AccountDisplayImageChanged;
		}
		
		public override void SwitchTo ()
		{
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/Toolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
			MenuUtility.FillMenuBar ("/Galaxium/Gui/MSN/ContainerWindow/Menu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
			
			Update ();
		}
		
		public override void SwitchFrom ()
		{
			
		}
		
		public override IEntity GetEntity (string uid, string name)
		{
			IContact contact = _conversation.ContactCollection.GetContactByName (name);
			
			if (contact == null)
			{
				contact = new MsnContact (_conversation.Session as MsnSession, uid);
				contact.DisplayName = name;
			}
			
			return contact;
		}
		
		private void HandleEventReceived(object sender, MessageEventArgs e)
		{
			_message_display.AddMessage (e.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void HandleMessageReceived(object sender, MessageEventArgs e)
		{
			_message_display.AddMessage (e.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
			
			SoundSetUtility.Play(Sound.MessageReceived);
		}
		
		protected override void MessageEntryTextSubmitted (object sender, SubmitTextEventArgs args)
		{
			if (args.Text.Trim ().Length == 0)
				return;
			
			MsnContact contact = Conversation.PrimaryContact as MsnContact;
			
			if (contact == null)
			{
				Anculus.Core.Log.Error ("There is no primary contact to send messages to in this conversation!");
				return;
			}
			
			if (!(_conversation as MsnConversation).CanSendMessage)
			{
				_message_display.AddEvent (GettextCatalog.GetString ("Unable to send a message to this contact"));
				return;
			}
			
			IMessage msg = new Message (MessageFlag.Message, _conversation.Session.Account, null, DateTime.Now);
			msg.Style = GetStyle ();
			msg.SetMarkup (args.Text, null);
			
			ProcessOutgoingMessage (msg);
			
			_messageEntry.Clear ();
			
			_message_display.AddMessage (msg);
			
			args.Submitted = true;
		}
		
		protected override void MessageEntryKeyRelease (object sender, KeyReleaseEventArgs args)
		{
			(_conversation as MsnConversation).Typing = _messageEntry.Text.Length > 0;
		}
		
		private void ConversationEstablished (object sender, ConversationEventArgs args)
		{
			
		}
		
		private void ConversationClosed (object sender, ConversationEventArgs args)
		{
			
		}
		
		void ConversationContactJoined (object sender, ContactActionEventArgs args)
		{
			AdjustForContacts ();
			
			if (args.Contact != Conversation.PrimaryContact)
				AddEventHandlers (args.Contact as MsnContact);
		}
		
		void ConversationContactLeft (object sender, ContactActionEventArgs args)
		{
			AdjustForContacts ();
			
			if (args.Contact != Conversation.PrimaryContact)
				RemoveEventHandlers (args.Contact as MsnContact);
		}
		
		void AddEventHandlers (MsnContact contact)
		{
			contact.DisplayImageChange += ContactDisplayImageChanged;
			contact.DisplayMessageChange += ContactDisplayMessageChanged;
			contact.DisplayNameChange += ContactDisplayNameChanged;
			contact.PresenceChange += ContactPresenceChanged;
			contact.SupressChange += ContactSupressChanged;
		}
		
		void RemoveEventHandlers (MsnContact contact)
		{
			contact.DisplayImageChange -= ContactDisplayImageChanged;
			contact.DisplayMessageChange -= ContactDisplayMessageChanged;
			contact.DisplayNameChange -= ContactDisplayNameChanged;
			contact.PresenceChange -= ContactPresenceChanged;
			contact.SupressChange -= ContactSupressChanged;
		}
		
		void ConversationAllContactsLeft (object sender, ConversationEventArgs args)
		{
			
		}
		
		private void ConversationMessageReceived (object sender, MessageEventArgs args)
		{
			_lastReceivedTime = args.Message.TimeStamp;
			
			ResetTypingNotification ();
		}
		
		private void ConversationInkReceived (object sender, InkEventArgs args)
		{
			//_message_display.AddMessage (MessageTypeEnum.Message | MessageTypeEnum.Image, args.Source, args.InkData);
			
			EmitBecomeActive ();
		}
		
		private void ConversationNudgeReceived (object sender, ContactEventArgs args)
		{
			if (Configuration.Conversation.Section.GetBool("WindowShake", true))
				_window.Shake();
			
			SoundSetUtility.Play(Sound.Nudge);
		}
		
		private void ConversationContactTyping(object sender, ContactEventArgs args)
		{
			_activityImage.Animation = IconUtility.GetAnimation ("galaxium-typing_anim");
			
			if (_typingTimer != 0)
				TimerUtility.ResetCallback (_typingTimer);
			else
			{
				_typingTimer = TimerUtility.RequestCallback (delegate
				{
					ResetTypingNotification ();
				}, 6000);
			}
			
			_status_label.Text = Message.Strip (args.Contact.DisplayIdentifier, args.Contact, null) +
				GettextCatalog.GetString (" is now typing a message...");
			EmitBecomingActive ();
		}
		
		private void ContactSupressChanged (object sender, EntityChangeEventArgs<bool> args)
		{
			if ((Conversation.PrimaryContact as MsnContact).SupressImage)
				_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
			else
			{
				try
				{
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (new Gdk.Pixbuf(Conversation.PrimaryContact.DisplayImage.ImageBuffer), 96, 96));
				}
				catch
				{
					_contact_image.FadeTo (PixbufUtility.GetScaledPixbuf (IconUtility.GetIcon ("galaxium-displayimage", IconSizes.Huge), 96, 96));
				}
			}
		}
		
		private void ConversationWinkReceived (object sender, WinkEventArgs args)
		{
			EmitBecomeActive ();
			
			DisplayWink (args.Wink);
		}
		
		void ConversationVoiceClipReceived (object sender, VoiceClipEventArgs args)
		{
			EmitBecomeActive ();
			
			args.Clip.Request (delegate
			{
				SoundUtility.Play (args.Clip.CacheFilename);
			});
		}
		
		void ConversationWebcamInviteReceived (object sender, InviteReceivedEventArgs args)
		{
			Viewport viewport = new Viewport ();
			WebcamWidget widget = new WebcamWidget (Session.Account, false);
			
			viewport.ShadowType = ShadowType.In;
			
			viewport.Add (widget);
			_contact_book.AddWidget (viewport);
			
			widget.Play();
			
			_contact_book.ShowWidget (viewport);
			_contact_book.ShowAll();
			_contact_book.Page = _contact_book.PageNum (viewport);
			
			_webcam = args.Application as P2PViewWebcam;
			_webcam.VideoPlayer = widget.VideoPlayer;
			_webcam.P2PSession.Closed += WebcamClosed;
			_webcam.P2PSession.Activated += WebcamActivated;
			
			EmitBecomeActive ();
			
			//TODO: we need a gui to display/accept/decline invitations
			_webcam.P2PSession.WaitingForLocal += delegate
			{
				_webcam.Accept ();
			};
		}
		
		public void ShowMyWebcam ()
		{
			Viewport viewport = new Viewport ();
			WebcamWidget widget = new WebcamWidget (Session.Account, true);
			
			viewport.ShadowType = ShadowType.In;
			
			viewport.Add (widget);
			_own_book.AddWidget (viewport);
			
			widget.Play();
			
			_own_book.ShowWidget (viewport);
			_own_book.ShowAll();
			_own_book.Page = _own_book.PageNum (viewport);
		}
		
		void WebcamActivated (object sender, EventArgs args)
		{
			_contact_image.ShowLoading ();
			_contact_image.SetSizeRequest (120, 96);
		}
		
		void WebcamClosed (object sender, EventArgs args)
		{
			MsnContact contact = _webcam.Remote;
			_webcam = null;
			
			_contact_image.SetSizeRequest (96, 96);
			
			if (_webcam == null)
				SwitchContactImage (contact);
		}
		
		void ContactDisplayNameChanged (object sender, EntityChangeEventArgs<string> args)
		{
			_contactList.QueueDraw();
		}
		
		void ContactDisplayMessageChanged (object sender, EntityChangeEventArgs<string> args)
		{
			GeneratePersonalLabel ();
		}
		
		void ContactDisplayImageChanged (object sender, EntityChangeEventArgs<IDisplayImage> args)
		{
			MsnContact contact = args.Entity as MsnContact;
			
			if (contact == _conversation.PrimaryContact)
			{
				// If the contact has an image, but we've not yet got the data
				// don't switch the image, this will be called again once the data
				// is retrieved
				if ((contact.DisplayImage != null) && (contact.DisplayImage.ImageBuffer.Length == 0))
					return;
				
					if (_webcam == null)
						SwitchContactImage (contact);
			}
		}
		
		void ContactPresenceChanged (object sender, EntityChangeEventArgs<IPresence> args)
		{
			MsnContact contact = args.Entity as MsnContact;
			
			_personalImage.Pixbuf = IconUtility.StatusLookup (contact.Presence, IconSizes.Small);
		}
		
		private void AccountDisplayImageChanged (object sender, EventArgs args)
		{
			_own_image.FadeTo (GenerateOwnImage ());
		}
		
		void ResetTypingNotification ()
		{
			if (_typingTimer != 0)
			{
				TimerUtility.RemoveCallback (_typingTimer);
				_typingTimer = 0;
			}
			
			if (_lastReceivedTime > DateTime.MinValue)
				_status_label.Text = GettextCatalog.GetString ("Last message received at: {0} - {1}", _lastReceivedTime.ToShortTimeString (), _lastReceivedTime.ToShortDateString ());
			else
				_status_label.Text = string.Empty;
			
			_activityImage.Pixbuf = IconUtility.GetIcon ("galaxium-typing_stopped");
		}
		
		private void ProcessOutgoingMessage (IMessage message)
		{
			// This is where we spawn to send the message in question.
			(_conversation as MsnConversation).SendMessage (message);
			
			SoundSetUtility.Play (Sound.MessageSent);
		}
		
		public void SendNudge ()
		{
			(_conversation as MsnConversation).SendNudge();
			
			if (Configuration.Conversation.Section.GetBool("WindowShake", true))
				_window.Shake ();
			
			SoundSetUtility.Play(Sound.Nudge);
		}
		
		public void SendFile ()
		{
			SendFileDialog dialog = new SendFileDialog (Conversation.PrimaryContact);
			
			// Here we would fill the dialog with the options we intend to use to send the file.
			
			ResponseType response = (ResponseType) dialog.Run ();
			
			if (response == ResponseType.Apply)
			{
				SendFile(dialog.Filename);
			}
			
			dialog.Destroy();
		}
		
		public override void SendFile (string filename)
		{
			if (Conversation.PrimaryContact == null)
			{
				Anculus.Core.Log.Warn ("There is no primary contact in this conversation to send files to!");
				return;
			}
			
			// Check to make sure that the file exists.
			if (File.Exists (filename))
			{
				MsnFileTransfer transfer = new MsnFileTransfer (Conversation.Session, Conversation.PrimaryContact, filename);
				
				MsnP2PUtility.Add (transfer.P2PTransfer);
				(Conversation.Session as MsnSession).EmitTransferInvitationSent (new FileTransferEventArgs (transfer));
			}
			else
			{
				string error = GettextCatalog.GetString ("The URI of the file you dropped is invalid! ('{0}'){1}", filename, Environment.NewLine);
				MessageDialog errordialog = new MessageDialog (null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, error);
				errordialog.Run ();
				errordialog.Destroy ();
			}
		}
		
		void AdjustForContacts ()
		{
			if (_window.CurrentWidget == this)
				_window.GenerateTitle ();
			
			if (_conversation.ContactCollection.Count > 1)
			{
				ShowBox (ChatWidgetPositions.RightDisplayPane);
				HideBox (ChatWidgetPositions.RightInput);
				
				_window.Toolbar.ShowAll();
				_entryToolbar.ShowAll();
				
				ShowBox (ChatWidgetPositions.LeftInput);
				HideBox (ChatWidgetPositions.Identification);
			}
			else
			{
				HideBox (ChatWidgetPositions.RightDisplayPane);
				ShowBox (ChatWidgetPositions.RightInput);
				
				if (Conversation.PrimaryContact != null)
				{
					MsnContact contact = Conversation.PrimaryContact as MsnContact;
					
					if (contact.ShowActionToolbar)
						_window.Toolbar.ShowAll();
					else
						_window.Toolbar.Visible = false;
					
					if (contact.ShowInputToolbar)
						_entryToolbar.ShowAll();
					else
						_entryToolbar.Visible = false;
					
					if (contact.ShowAccountImage)
						ShowBox (ChatWidgetPositions.LeftInput);
					else
						HideBox (ChatWidgetPositions.LeftInput);
					
					if (contact.ShowContactImage)
						ShowBox (ChatWidgetPositions.RightInput);
					else
						HideBox (ChatWidgetPositions.RightInput);
					
					if (contact.ShowPersonalMessage)
						ShowBox (ChatWidgetPositions.Identification);
					else
						HideBox (ChatWidgetPositions.Identification);
					
					GeneratePersonalLabel ();
					
					if (_webcam == null)
						SwitchContactImage (contact);
				}
				else
				{
					Anculus.Core.Log.Error ("There is no primary contact for the conversation this chat widget represents!");
				}
			}
		}
		
		void ConversationMessageSendFailed (object sender, MessageEventArgs args)
		{
			_message_display.AddError (GettextCatalog.GetString ("The message \"{0}\" could not be sent within a reasonable amount of time!", args.Message.Text));
			EmitBecomeActive ();
		}
		
		void ConversationCapabilitiesChanged (object sender, EventArgs args)
		{
			if (_window.CurrentWidget == this)
			{
				Update ();
			}
		}
		
#region Font Handling
		public override void LoadFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];
			
			_messageEntry.Family = config.GetString (Configuration.MessageDisplay.Family.Name, Configuration.MessageDisplay.Family.Default);
			_messageEntry.Size = config.GetDouble (Configuration.MessageDisplay.Size.Name, Configuration.MessageDisplay.Size.Default);
			_messageEntry.ColorInt = config.GetInt (Configuration.MessageDisplay.Color.Name, Configuration.MessageDisplay.Color.Default);
			_messageEntry.Bold = config.GetBool (Configuration.MessageDisplay.Bold.Name, Configuration.MessageDisplay.Bold.Default);
			_messageEntry.Italic = config.GetBool (Configuration.MessageDisplay.Italic.Name, Configuration.MessageDisplay.Italic.Default);
			_messageEntry.Underline = config.GetBool (Configuration.MessageDisplay.Underline.Name, Configuration.MessageDisplay.Underline.Default);
			_messageEntry.Strikethrough = config.GetBool (Configuration.MessageDisplay.Strikethrough.Name, Configuration.MessageDisplay.Strikethrough.Default);
			
			MenuUtility.FillToolBar ("/Galaxium/Gui/MSN/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
		}
		
		public override void SaveFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];

			config.SetString (Configuration.MessageDisplay.Family.Name, _messageEntry.Family);
			config.SetDouble (Configuration.MessageDisplay.Size.Name, _messageEntry.Size);
			config.SetInt (Configuration.MessageDisplay.Color.Name, _messageEntry.ColorInt);
			config.SetBool (Configuration.MessageDisplay.Bold.Name, _messageEntry.Bold);
			config.SetBool (Configuration.MessageDisplay.Italic.Name, _messageEntry.Italic);
			config.SetBool (Configuration.MessageDisplay.Underline.Name, _messageEntry.Underline);
			config.SetBool (Configuration.MessageDisplay.Strikethrough.Name, _messageEntry.Strikethrough);
			
			if (_fontChangedHandlers[_conversation.Session] != null)
				_fontChangedHandlers[_conversation.Session] (this, EventArgs.Empty);
		}
		
		private IMessageStyle GetStyle ()
		{
			MsnMessageStyle style = new Galaxium.Protocol.Msn.MsnMessageStyle (false, MsnCharacterSet.Default, MsnPitchFamily.Default);

			style.Font = _messageEntry.Family;
			style.FontSize = (int)_messageEntry.Size;
			style.Bold = _messageEntry.Bold;
			style.Italic = _messageEntry.Italic;
			style.Underline = _messageEntry.Underline;
			style.Strikethrough = _messageEntry.Strikethrough;
			style.Foreground = (ARGBColor)_messageEntry.ColorInt;
			
			return style;
		}
#endregion
		
		protected override void OwnImageButtonPressEvent (object sender, ButtonPressEventArgs args)
		{
			if (args.Event.Type == Gdk.EventType.TwoButtonPress)
				new SetDisplayImageCommand().Run ();
		}
		
		internal void DisplayWink (MsnWink wink)
		{
			if (!GtkUtility.EnableSwf)
			{
				_message_display.AddError (GettextCatalog.GetString ("Install swfdec 0.6+ to enable wink playback"));
				return;
			}
			
			wink.Request (delegate
			{
				// We now have the wink data
				
				//Log.Debug ("Ready to play wink {0}", args.Wink.Animation);
				
				//TODO: Queue display until window becomes active
				try
				{
					new WinkDisplay (wink, GetBox (ChatWidgetPositions.LeftDisplayPane)).Show ();
				}
				catch (Exception ex)
				{
					Anculus.Core.Log.Error (ex, "Error displaying wink");
				}
			});
		}
	}
}
